package org.toves.checklua.scan;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TokenScanner {
	private static class Option {
		private Pattern patt;
		private String regex;
		private TokenType token;
		
		public Option(Pattern patt, String regex, TokenType token) {
			this.patt = patt;
			this.regex = regex;
			this.token = token;
		}
	}
	
	private ArrayList<Option> option;
	private Pattern combined;
	private int tabStop;
	
	public TokenScanner() {
		this.option = new ArrayList<Option>();
		this.combined = null;
		this.tabStop = 8;
	}
	
	public void setTabStop(int tabStop) {
		this.tabStop = tabStop;
	}
	
	public int getTabStop() {
		return this.tabStop;
	}
	
	public void addRegex(String re, TokenType token) {
		Pattern patt = Pattern.compile(re);
		option.add(new Option(patt, re, token));
		combined = null;
	}
	
	public void addLiteral(String lit, TokenType token) {
		String re = Pattern.quote(lit);
		Pattern patt = Pattern.compile(re);
		option.add(new Option(patt, re, token));
		combined = null;
	}
	
	public void addLiteral(String[] lits, TokenType token) {
		StringBuilder reBuilder = new StringBuilder();
		for (String lit : lits) {
			if (reBuilder == null) {
				reBuilder = new StringBuilder(Pattern.quote(lit));
			} else {
				reBuilder.append("|" + Pattern.quote(lit));
			}
		}
		String re = reBuilder.toString();
		Pattern patt = Pattern.compile(re);
		option.add(new Option(patt, re, token));
		combined = null;
	}
	
	private void ensureCompiled() {
		if (combined == null) {
			StringBuilder reBuilder = new StringBuilder();
			for (Option opt : option) {
				reBuilder.append("(" + opt.regex + ")|");
			}
			reBuilder.append("."); // for the TokenError case
			combined = Pattern.compile(reBuilder.toString());
		}
	}
	
	public List<Token> scan(CharSequence input) {
		ensureCompiled();
		ArrayList<Token> ret = new ArrayList<Token>();
		Matcher m = combined.matcher(input);
		int line = 1;
		int column = 0;
		boolean lastValid = true;
		while (m.find()) {
			String text = m.group();
			boolean matchFound = false;
			TokenType type = TokenType.TOKEN_ERROR;
			for (Option opt : option) {
				if (opt.patt.matcher(text).matches()) {
					matchFound = true;
					type = opt.token;
					break;
				}
			}
			if (!matchFound && !lastValid) {
				Token last = ret.get(ret.size() - 1);
				Token repl = new Token(type, last.getLine(), last.getColumn(),
						last.getText() + text);
				ret.set(ret.size() - 1, repl);
			} else {
				lastValid = matchFound;
				if (type != null) {
					type = type.process(this, text);
					if (type != null) {
						ret.add(new Token(type, line, column + 1, text));
					}
				}
			}
			
			int start = 0;
			int next = text.indexOf('\n');
			while (next >= 0) {
				line++;
				column = 0;
				start = next + 1;
				next = text.indexOf('\n', start);
			}
			next = text.indexOf('\t', start);
			while (next >= 0) {
				column += (next - start);
				column += tabStop - (column % tabStop);
				start = next + 1;
				next = text.indexOf('\t', start);
			}
			column += text.length() - start;
		}
		ret.add(new Token(TokenType.TOKEN_EOF, line, column + 1, "EndOfInput"));
		return ret;
	}
}
